#include <iomanip>

#include "sc_client.h"
#include "filesystem/be_filesystem.h"
#include "gui/exitpanel.h"
#include "gui/enginesettingspanel.h"
#include "gui/logbuffermessage.h"
#include "../gui/infobar.h"
#include "../gui/carsettings.h"

ScClient::ScClient() :
	m_camera(SIMD_HALF_PI/2, 1024.0f/768.0f, 0.05f, 5000),
	m_cameraFollow(SIMD_HALF_PI/2, 1024.0f/768.0f, 0.05f, 5000)
{
	settings = Settings::Instance();

	setting_drawscene = settings->getCVarPtr("drawscene");
	setting_follow = settings->getCVarPtr("follow");
	setting_camera_follow = settings->getCVarPtr("camera_follow");
	
	// BULLET SETUP
	m_dynamicsWorld = m_physicsSystem.createWorld();

	// create gl window
	glwindow.create("Stunt Coureur", 1024, 768);	

	m_canvas.reset(new Canvas);
	m_graphics.reset(new ScGraphics);
	m_joystick.reset(new Joystick);

	// SET CAMERA POSITION
	m_sceneNodeCamera.setOrigin( btVector3( 600, 70, 0 ) );
	m_camera.setSceneNode(&m_sceneNodeCamera);
	m_cameraFollow.setSceneNode(&m_sceneNodeCameraFollow);

	// push outgoing message, version
	stringstream versionmessage;
	versionmessage << "v " << settings->getCVarS("version") << ";";
	cerr << versionmessage.str()  << endl;
	pushOutgoingmessage(versionmessage.str());

	// register panels
	m_canvas->addWidgetPanel( "panel_exit", 		new Exitpanel() );
	m_canvas->addWidgetPanel( "panel_enginesettings", 	new Enginesettingspanel() );
	m_canvas->addWidgetPanel( "panel_logbuffermessage",	new Logbuffermessage() );
	m_canvas->addWidgetPanel( "panel_infobar",		new Infobar() );
	m_canvas->addWidgetPanel( "panel_carsettings",		new Carsettings() );
	
	speedo = new Speedo();
	m_canvas->addWidgetPanel( "panel_speedo",		speedo );
	
	m_canvas->setDefaultZAxis();	
}

void ScClient::process( float timeDelta )
{
	glwindow.process();

	parseIncomingMessages();

	//Update speedo
	if ( me !=0 && me->isJoined() )
	{
		speedo->setSpeed( me->getCar()->m_vehicle->getCurrentSpeedKmHour() );
	};

	//Update player camera
	btTransform offset2;
	offset2.setIdentity();

	btTransform offset;
	offset.setIdentity();

	if ( me && me->isJoined() )
	{
		me->getCar()->applyForcesClient( timeDelta, me->getInputtype() );

		// send steering
		stringstream msg;
		msg << "ca " << "0" << " " << me->getCar()->gVehicleSteering << ";";
		
		// send throttle
// 		msg << "ca " << "1" << " " << (int)me->getCar()->m_forw - (int)me->getCar()->m_back << ";";
		msg << "ca " << "1" << " " << me->getCar()->m_throttle << ";";

		// send braking
		msg << "ca " << "2" << " " << me->getCar()->m_braking << ";";
		
		pushOutgoingmessage(msg.str());
		
		// set drawposition for camera
		offset2.setFromOpenGLMatrix( me->getCar()->body.m_bodyparts[0]->drawposition );
		if( *setting_follow && !*setting_camera_follow )
		{
			me->getCar()->m_drawChassis=false;
			offset.setOrigin(btVector3( 0.0f, 0.15f, 0.75f ));
		}
		else
		{
			me->getCar()->m_drawChassis=true;
			offset.setOrigin(btVector3( 0, 1.25f, 6.5f ));
		}
	}

	//Update camera and sky box transforms
	btTransform skyBoxTransform;
	skyBoxTransform.setIdentity();

	if ( !*setting_follow )
	{
		m_graphicsSystem.applyCamera(m_camera);
		skyBoxTransform=m_camera.getSceneNode()->getTransform();
	}
	else
	{
		if ( me->isJoined() )
		{
			BeSceneNode* const sceneNode=m_cameraFollow.getSceneNode();
			if(sceneNode)
			{
				sceneNode->setTransform(offset2*offset);
			}
			m_graphicsSystem.applyCamera(m_cameraFollow);
			skyBoxTransform=m_cameraFollow.getSceneNode()->getTransform();
		}
	}

	//Update map
	if ( m_map )
	{
		m_map->update(timeDelta);
		m_map->setSkyBoxTransform(skyBoxTransform);
	}
}

void ScClient::parseIncomingMessages()
{
	for ( unsigned int i=0; i < m_messages_in.size(); i++ )
	{
		string clientmessage = m_messages_in[i];

		string msg = parseH.returnUntillStrip( ";", clientmessage );
// 		cerr << "msg: " << msg << endl;
		while ( msg.size() > 0 )
		{
			unsigned int playerid;
			float forwardWheelSumSide;

			string ntype = parseH.returnUntillStrip( " ", msg );
			if ( ntype == "pl" )
			{
				msg.append(" "); // adding a space for parsing ease
				unsigned int myid(-1);
				parseH.parseUnsignedInt( msg, myid );
// 				cerr << "my id" << myid << endl;
				while (  parseH.parseUnsignedInt(msg, playerid) )
				{
					boost::shared_ptr<Player> p(new Player( playerid ));
					m_players.push_back(p);
					unsigned int joined=0;
					parseH.parseUnsignedInt( msg, joined );
					if ( joined == 1 )
					{
						playerJoin(p);
					}
					if ( p->getId() == myid )
					{
// 						cerr << "CLIENT: player is me" << endl;
						me = p;
					}
				}
			}

			// car positions
			else if ( ntype == "cp" )
			{
				msg.append(" "); // adding a space for parsing ease
				while ( parseH.parseUnsignedInt(msg, playerid) )
				{
					btScalar speed(0);
					boost::shared_ptr<Player> player = findPlayer(playerid);
// 					cerr << "CLIENT: found player " << foundindex << endl;
					if (player && ( player->getId() == me->getId() ))
					{
						parseH.parseFloat(msg, speed);
					}

					btVector3 pos(0,0,0);
					parseH.parseVector3(msg, pos);
					btVector3 rot(0,0,0);
					parseH.parseVector3(msg, rot);
					btScalar wheelrot(0);
					parseH.parseFloat(msg, wheelrot);

					if ( player && player->isJoined())
					{
						static_cast<BodypartRigid*>(player->getCar()->body.m_bodyparts[0].get())->m_motionState->m_graphicsWorldTrans.setIdentity();
						static_cast<BodypartRigid*>(player->getCar()->body.m_bodyparts[0].get())->m_motionState->m_graphicsWorldTrans.setOrigin( pos );
						static_cast<BodypartRigid*>(player->getCar()->body.m_bodyparts[0].get())->m_motionState->m_graphicsWorldTrans.getBasis().setEulerZYX( rot.getZ(), rot.getY(), rot.getX() ); // -1.5707f = 90 degrees
						player->getCar()->m_vehicle->setSpeed( speed );
						player->getCar()->m_vehicle->setSteeringValue( wheelrot, 0);
						player->getCar()->m_vehicle->setSteeringValue( wheelrot, 1);
						player->getCar()->updatePosition();
					}
				}
			}

			// force feedback
			else if ( ntype == "ff" && parseH.parseFloat(msg, forwardWheelSumSide) )
			{
				m_joystick->applyForce( forwardWheelSumSide ); // lastreltime
				
				
				
				me->getCar()->m_vehicle->forwardWheelSumSide = forwardWheelSumSide;
				
				
				
			}

			// map change
			else if ( ntype == "mc" )
			{
				mapChange(msg);
			}

			// player joins
			else if ( ntype == "jo" && parseH.parseUnsignedInt(msg, playerid) )
			{
				playerJoin( findPlayer(playerid) );
			}

			// player unjoins
			else if ( ntype == "uj" && parseH.parseUnsignedInt(msg, playerid) )
			{
				playerUnjoin( findPlayer(playerid) );
			}

			// new player
			else if ( ntype == "np" && parseH.parseUnsignedInt(msg, playerid) )
			{
				playerConnect( findPlayer(playerid), playerid );
			}

			// player disconnects
			else if ( ntype == "pd" && parseH.parseUnsignedInt(msg, playerid) )
			{
				playerDisconnect(findPlayer(playerid));
			}

			// player finished race
			else if ( ntype == "pf" && parseH.parseUnsignedInt(msg, playerid) )
			{
				playerFinish(findPlayer(playerid), msg);
			}
			
			else if ( ntype == "ms" )
			{
				newLogMessage(msg);
			}

			msg = parseH.returnUntillStrip( ";", clientmessage );
		}
	}
	m_messages_in.clear();
}

void ScClient::newLogMessage(const std::string& msg)
{
	Logbuffer::Instance()->add(msg);
	std::cout << msg << "\n";
}

void ScClient::draw()
{
	m_graphicsSystem.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	glViewport(0,0,*settings->winWidth,*settings->winHeight);

	// 3D
	glEnable(GL_DEPTH_TEST);
	if ( *setting_drawscene )
	{

		if( m_map )
		{
			m_map->drawSkyBox();
		}

		m_graphicsSystem.enable(GL_LIGHTING);
		m_graphicsSystem.useProgram(&m_graphics->m_program);
		for( unsigned int i=0; i < m_entities.size(); i++)
			m_entities[i]->draw();

		for( unsigned int i=0; i < m_players.size(); i++)
			if ( m_players[i]->isJoined() )
				m_players[i]->getCar()->draw();
		m_graphicsSystem.useProgram(0);
		m_graphicsSystem.disable(GL_LIGHTING);
	}

	// 3D DEBUGDRAW
	//if ( *drawdebug )
	{
		// 			glEnable(GL_DEPTH_TEST);
		// 			world->physicsDebugDrawWorld();
		// 			glDisable(GL_DEPTH_TEST);
	}

	glDisable(GL_DEPTH_TEST);

	// 2D
	if ( m_canvas->active )
	{
		// // 			update speedo
		// 			if ( world->m_sc_client->me !=0 && world->m_sc_client->me->isJoined() )
		// 			{
		// 				speedo->setSpeed( world->m_sc_client->me->getCar()->m_vehicle->getCurrentSpeedKmHour() );
		// 			};

		m_graphicsSystem.disable(GL_BLEND);
		m_graphicsSystem.disable(GL_ALPHA_TEST);
		glDepthMask( GL_FALSE );
		m_graphicsSystem.matrixLoadIdentity(GL_PROJECTION);
		m_graphicsSystem.matrixOrtho(GL_PROJECTION, 0, *settings->winWidth, *settings->winHeight, 0,  0, 1);
		m_graphicsSystem.matrixLoadIdentity(GL_MODELVIEW);
		m_graphicsSystem.matrixTranslate(GL_MODELVIEW, 0.5f, -0.5f, 0.0f); // pixel precision offset
		m_canvas->draw();	
		glDepthMask( GL_TRUE );
		m_graphicsSystem.enable(GL_ALPHA_TEST);
		m_graphicsSystem.enable(GL_BLEND);
		glColor4f(1,1,1,1);
	}
	//In debug mode ensure that there were no gl errors in the last frame
#ifdef _DEBUG
	assert(glGetError()==GL_NO_ERROR);
#endif
	SDL_GL_SwapBuffers();

}

boost::shared_ptr<Player> ScClient::findPlayer(unsigned int id)
{
	for ( unsigned int k=0; k < m_players.size(); k++ )
	{
		if ( m_players[k]->getId() == id )
		{
			return m_players[k];
		}
	}
	return boost::shared_ptr<Player>();
}

ScClient::~ScClient()
{
	cerr << "exiting client" << endl;
}

void ScClient::mapChange( const std::string& name )
{
	if ( name.size() > 0 )
	{
		cerr << "CLIENT: loading map " << name << endl;
		m_map = Map::LoadMap(name.c_str(), m_dynamicsWorld);
		m_map->upload();
		m_entities.push_back(m_map);
		m_map->updatePosition();
		cerr << "load map " << name << endl;
	}
}

void ScClient::playerConnect( boost::shared_ptr<Player> player, const unsigned int id )
{
	if ( !player )	//Player is expected to be 0 pointer
	{
		stringstream newlog; newlog << "player " << id << " connected";
		newLogMessage(newlog.str());
		m_players.push_back( boost::shared_ptr<Player>(new Player( id )) );
	}
}



void ScClient::playerDisconnect( boost::shared_ptr<Player> player )
{
	if ( player )
	{
		Players::iterator iterator = std::find(m_players.begin(), m_players.end(), player);
		if(iterator!=m_players.end())
		{
			stringstream newlog; newlog << "player " << player->getId() << " disconnected";
			newLogMessage(newlog.str());
			player->unjoin();
			m_players.erase( iterator );
		}
	}
}

void ScClient::playerJoin( boost::shared_ptr<Player> player )
{
	if(player)
	{
		stringstream newlog; newlog << "player " << player->getId() << " joined";
		newLogMessage(newlog.str());
		boost::shared_ptr<Car> car(new Car( player->getId(), m_map->startgrid, m_dynamicsWorld) );
		car->upload();
		player->join ( car );
	}
}

void ScClient::playerUnjoin( boost::shared_ptr<Player> player )
{
	if ( player )
	{
		stringstream newlog; newlog << "player " << player->getId() << " left";
		newLogMessage(newlog.str());
		player->unjoin();
	}
}

void ScClient::playerFinish( boost::shared_ptr<Player> player, const std::string& msg )
{
	if ( player )
	{
		stringstream newlog; newlog << "player " << player->getId() << " finished: " << msg;
		newLogMessage(newlog.str());
	}
}
